转载自掘金网络,原文链接:https://juejin.im/post/5d84456851882556f33d5fb0
先执行一段代码
a/index.js
1 | modulex.exports = 1; |
useA.js
1 | const a = require('./a'); |
执行useA.js
1 | Module { |
通过上面的代码我们可以想到几个问题
- useA.js是如何找到a/index.js和express的
- a.js的module.exports在useA.js中是如何获取到的
- console.log(global.require); 输出的是undefined,说明require不挂载在全局对象上,那为什么可以使用
- 模块中并没有申明module变量,为什么可以输出一个对象
本篇文章就通过这四个问题,来逐渐解析node模块化机制
模块查找规则
- 首先我看一下require()函数在node内部是如何定义的
1 | function require(path) { |
- mod.require
1 | Module.prototype.require = function(id) { |
- Module._load函数
1 | Module._load = function(request, parent, isMain) { |
- Module._resolveFilename 如何查找到文件路径的
1 | Module._resolveFilename = function(request, parent, isMain, options) { |
- Module._resolveLookupPaths粗略查找文件可能出现的范围
1 | Module._resolveLookupPaths = function(request, parent, newReturn) { |
- 精确查找文件路径
1 | Module._findPath = function(request, paths, isMain) { |
现在回答第一个问题,node是如何找到模块的
阶段1. 粗查阶段
- 如果是node核心模块,就直接返回模块名称
- 如果是引入的第三方npm模块,会返回父级所在文件夹下的node_modules,父父级所在文件夹下的node_modules,依次递归,一直到/node_modules和用户名下的.node_modules以及全局环境变量配置的全局安装的模块文件夹组成的数组
- 如果是相对路径引入的模块,会将相对路径和父级路径之间进行一个path.resolve(),然后返回
阶段2. 精确查找,获取文件绝对路径
以require(‘express’)为例
- 先尝试加载node_modules/express,这种没有扩展名的文件是否存在
- 尝试按照扩展名规则查找,依次判断node_modules文件夹下.js .json .node结尾的文件名为express的文件是否存在,返回文件的绝对路径
- 判断node_modules/express文件夹下的package.json是否存在,如果存在,返回main字段指定的文件的绝对路径
- 判断node_modules/express/index.js是否存在,存在返回对应文件绝对路径
解析模块
- Module._resolveFilename(request, parent, isMain);获取到文件绝对路径之后,执行tryModuleLoad(module, filename); 尝试加载模块
- tryModuleLoad 直接调用Module.prototype.load函数
1 | Module.prototype.load = function(filename) { |
- .js .json .node结尾的文件,node的解析规则
1 | // Native extension for .js |
json会直接读取文件内容,JSON.parse直接输出,.node文件会使用process.dlopen()执行文件,这两种文件处理比较简单,这里详细分析.js结尾的文件
- module._compile执行js文件编译
1 | Module.prototype._compile = function(content, filename) { |
- Module.wrap通过字符串拼接,在代码外包含一个函数
1 | Module.wrap = function(script) { |
- 调用vm.runInThisContext将字符串转为可执行的js函数
- compiledWrapper.call(this.exports, this.exports, require, this, filename, dirname); 执行函数,module.exports, require函数, module作为三个参数,在函数内部给module.exports进行赋值,模块内部可以使用require函数加载模块,可以获取到module变量,描述当前模块的信息,
模拟实现require函数
1 | const path = require('path'); |